package com.tsd.core.openweixin.utils;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.tsd.core.openweixin.WechatAccessException;
import com.tsd.core.openweixin.model.AccessToken;
import com.tsd.core.openweixin.model.AccessTokenOauth2;
import com.tsd.core.openweixin.model.JsapiTicket;
import com.tsd.core.openweixin.model.WechatUser;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.*;

/**
 *
 */
public class WechatUtils {

    private static final Logger logger = LoggerFactory.getLogger(WechatUtils.class);
    private static final TypeAdapter<Boolean> booleanAsIntAdapter = new TypeAdapter<Boolean>() {
        @Override
        public void write(JsonWriter out, Boolean value) throws IOException {
            if (value == null) {
                out.nullValue();
            } else {
                out.value(value);
            }
        }

        @Override
        public Boolean read(JsonReader in) throws IOException {
            JsonToken peek = in.peek();
            switch (peek) {
                case BOOLEAN:
                    return in.nextBoolean();
                case NULL:
                    in.nextNull();
                    return null;
                case NUMBER:
                    return in.nextInt() != 0;
                case STRING:
                    return Boolean.parseBoolean(in.nextString());
                default:
                    throw new IllegalStateException("Expected BOOLEAN or NUMBER but was " + peek);
            }
        }
    };
    static Gson gson;

    public static String sign(String timestamp, String nonce, String token) {
        if (timestamp == null || nonce == null || token == null) {
            throw new IllegalArgumentException("Argument cannot be null.");
        }
        List<String> params = new ArrayList<String>();
        params.add(timestamp);
        params.add(nonce);
        params.add(token);
        logger.debug("before sort {}", StringUtils.join(params, ","));
        Collections.sort(params);
        logger.debug("end sort {}", StringUtils.join(params, ","));
        String result = DigestUtils.sha1Hex(StringUtils.join(params, ""));
        logger.debug("sha1hex {}", result);
        return result;
    }

    /**
     * 根据appid、secret获取AccessToken
     * <p>
     * 见：http://mp.weixin.qq.com/wiki/15/54ce45d8d30b6bf6758f68d2e95bc627.html
     * access_token是公众号的全局唯一票据，公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。access_token的存储至少要保留512个字符空间。access_token的有效期目前为2个小时，需定时刷新，重复获取将导致上次获取的access_token失效。
     * <p>
     * 公众平台的API调用所需的access_token的使用及生成方式说明：
     * <p>
     * 1、为了保密appsecrect，第三方需要一个access_token获取和刷新的中控服务器。而其他业务逻辑服务器所使用的access_token均来自于该中控服务器，不应该各自去刷新，否则会造成access_token覆盖而影响业务；
     * <p>
     * 2、目前access_token的有效期通过返回的expire_in来传达，目前是7200秒之内的值。中控服务器需要根据这个有效时间提前去刷新新access_token。在刷新过程中，中控服务器对外输出的依然是老access_token，此时公众平台后台会保证在刷新短时间内，新老access_token都可用，这保证了第三方业务的平滑过渡；
     * <p>
     * 3、access_token的有效时间可能会在未来有调整，所以中控服务器不仅需要内部定时主动刷新，还需要提供被动刷新access_token的接口，这样便于业务服务器在API调用获知access_token已超时的情况下，可以触发access_token的刷新流程。
     * <p>
     * 如果第三方不使用中控服务器，而是选择各个业务逻辑点各自去刷新access_token，那么就可能会产生冲突，导致服务不稳定。
     * <p>
     * 公众号可以使用AppID和AppSecret调用本接口来获取access_token。AppID和AppSecret可在微信公众平台官网-开发者中心页中获得（需要已经成为开发者，且帐号没有异常状态）。注意调用所有微信接口时均需使用https协议。
     * <p>
     * 接口调用请求说明
     * <p>
     * http请求方式: GET
     * <p>
     * https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET
     * <p>
     * 参数说明
     * 参数 	是否必须 	说明
     * grant_type 	是 	获取access_token填写client_credential
     * appid 	是 	第三方用户唯一凭证
     * secret 	是 	第三方用户唯一凭证密钥，即appsecret
     * <p>
     * 返回说明
     * <p>
     * 正常情况下，微信会返回下述JSON数据包给公众号：
     * <p>
     * {"access_token":"ACCESS_TOKEN","expires_in":7200}
     * <p>
     * 参数 	说明
     * access_token 	获取到的凭证
     * expires_in 	凭证有效时间，单位：秒
     * <p>
     * <p>
     * 错误时微信会返回错误码等信息，JSON数据包示例如下（该示例为AppID无效错误）:
     * <p>
     * {"errcode":40013,"errmsg":"invalid appid"}
     *
     * @param appid
     * @param secret
     * @return
     */
    public static AccessToken getAccessToken(String appid, String secret) {
        if (appid == null || secret == null) {
            throw new IllegalArgumentException("appid or secret was null.");
        }
        ClientConfig config = new DefaultClientConfig();
        Client client = Client.create(config);
        WebResource cosmsservice = client.resource("https://api.weixin.qq.com/cgi-bin/token");
        ClientResponse clientResponse = cosmsservice
                .queryParam("grant_type", "client_credential")
                .queryParam("appid", appid)
                .queryParam("secret", secret)
                .get(ClientResponse.class);

        if (clientResponse.getStatus() != 200) {
            throw new IllegalStateException("status error:" + clientResponse.getStatus());
        } else {
            Map result = new Gson().fromJson(clientResponse.getEntity(String.class), Map.class);

            if (result.get("errcode") != null) {
                throw new WechatAccessException(result.get("errcode") + "", result.get("errmsg") + "");
            } else {
                Long d = 10L * 60;//改为10分钟token过期 ((Double) result.get("expires_in")).longValue()-200;//在原来基础上减少200秒
                return new AccessToken((String) result.get("access_token"), (new Date()).getTime() + d * 1000L);
            }

        }
    }

    /**
     * 获取JsapiTicket
     * https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token=VOT-flCFMjwaQwLGZnaTDrkTQ0Xbvh3h1Q4lke75Ifz54JQfuJ2oDpaHyMz0JmqOr3G0qDqsrs1ioXZZAZu8hX9DHFkuXohDOlWrGSvxDek
     *
     * @param access_token
     * @return
     */
    public static JsapiTicket getJsapiTicket(String access_token) {
        if (access_token == null) {
            throw new IllegalArgumentException("access_tokenwas null.");
        }
        ClientConfig config = new DefaultClientConfig();
        Client client = Client.create(config);
        WebResource cosmsservice = client.resource("https://api.weixin.qq.com/cgi-bin/ticket/getticket");
        ClientResponse clientResponse = cosmsservice
                .queryParam("type", "jsapi")
                .queryParam("access_token", access_token)
                .get(ClientResponse.class);

        if (clientResponse.getStatus() != 200) {
            throw new IllegalStateException("status error:" + clientResponse.getStatus());
        } else {
            JsapiTicket ticket = new Gson().fromJson(clientResponse.getEntity(String.class), JsapiTicket.class);
            Long d = 10L * 60;//改为10分钟token过期
//            Map result = new Gson().fromJson(clientResponse.getEntity(String.class), Map.class);
            //(ticket.getExpires_in()-200)
            ticket.setExpiresTimestemp(d * 1000L + (new Date()).getTime());
            //为了避免时间同步的问题，将有效期减少200秒，wudw-2015-05-25
//            if (result.get("errcode") != 0) {
//                throw new WechatAccessException(result.get("errcode") + "", result.get("errmsg") + "");
//            }
            return ticket;

        }
    }

    /**
     * 从oauth2中获取accessToken
     *
     * @param appid
     * @param secret
     * @param code
     * @return
     */
    public static AccessTokenOauth2 getAccessTokenFromOauth2(String appid, String secret, String code) {
        if (appid == null || secret == null) {
            throw new IllegalArgumentException("appid or secret was null.");
        }
        ClientConfig config = new DefaultClientConfig();
        Client client = Client.create(config);
        WebResource cosmsservice = client.resource("https://api.weixin.qq.com/sns/oauth2/access_token");
        ClientResponse clientResponse = cosmsservice
                .queryParam("grant_type", "authorization_code")
                .queryParam("appid", appid)
                .queryParam("secret", secret)
                .queryParam("code", code)
                .get(ClientResponse.class);

        if (clientResponse.getStatus() != 200) {
            throw new IllegalStateException("status error:" + clientResponse.getStatus());
        } else {
            Map result = new Gson().fromJson(clientResponse.getEntity(String.class), Map.class);

            if (result.get("errcode") != null) {
                throw new WechatAccessException(result.get("errcode") + "", result.get("errmsg") + "");
            } else {
                return new AccessTokenOauth2((String) result.get("access_token"), ((Double) result.get("expires_in")).longValue(),
                        (String) result.get("openid"), (String) result.get("refresh_token"),
                        (String) result.get("scope"));
            }

        }
    }

    static public WechatUser getUserInfo(String openId, String AccessTokenOauth2) {
        if (gson == null) {
            gson = new GsonBuilder()
                    .registerTypeAdapter(Boolean.class, booleanAsIntAdapter)
                    .registerTypeAdapter(boolean.class, booleanAsIntAdapter)
                    .create();
        }
        if (openId == null || AccessTokenOauth2 == null) {
            throw new IllegalArgumentException("openid or AccessTokenOauth2 was null.");
        }
        ClientConfig config = new DefaultClientConfig();
        Client client = Client.create(config);
        WebResource cosmsservice = client.resource("https://api.weixin.qq.com/sns/userinfo");
        ClientResponse clientResponse = cosmsservice
                .queryParam("access_token", AccessTokenOauth2)
                .queryParam("openid", openId)
                .queryParam("lang", "zh_CN")
                .get(ClientResponse.class);
        if (clientResponse.getStatus() != 200) {
            throw new IllegalStateException("status error:" + clientResponse.getStatus());
        } else {
            Map result = new Gson().fromJson(clientResponse.getEntity(String.class), Map.class);

            if (result.get("errcode") != null) {
                throw new WechatAccessException(result.get("errcode") + "", result.get("errmsg") + "");
            } else {
                String s = clientResponse.getEntity(String.class);
                return gson.fromJson(s, WechatUser.class);
            }

        }
    }

    public static String getOpenIdFromWx(String appId, String appSecret, String code) {
        AccessTokenOauth2 token = WechatUtils.getAccessTokenFromOauth2(appId, appSecret, code);
        String openId = token.getOpenId();
        return openId;
    }
}
